Skip to content

热更新 HMR

webpack-dev-server.js

  • 通过 express 启动一个服务器,监听文件编译结束,然后通过 socket 将文件 hash 发送给客户端
js
const path = require("path");
const fs = require("fs");
const express = require("express");
const mime = require("mime");
const webpack = require("webpack");
let config = require("./webpack.config");
let compiler = webpack(config);

class Server {
  constructor(compiler) {
    let lastHash;
    let sockets = [];
    compiler.hooks.done.tap("webpack-dev-server", (stats) => {
      lastHash = stats.hash;
      sockets.forEach((socket) => {
        socket.emit("hash", stats.hash);
        socket.emit("ok");
      });
    });
    let app = new express();
    compiler.watch({}, (err) => {
      console.log("编译成功");
    });

    const webpackDevMiddleware = (req, res, next) => {
      if (req.url === "/favicon.ico") {
        return res.sendStatus(404);
      }
      let filename = path.join(config.output.path, req.url.slice(1));
      try {
        let stats = fs.statSync(filename);
        if (stats.isFile()) {
          let content = fs.readFileSync(filename);
          res.header("Content-Type", mime.getType(filename));
          res.send(content);
        } else {
          next();
        }
      } catch (error) {
        return res.sendStatus(404);
      }
    };
    app.use(webpackDevMiddleware);
    this.server = require("http").createServer(app);
    let io = require("socket.io")(this.server);
    io.on("connection", (socket) => {
      sockets.push(socket);
      if (lastHash) {
        socket.emit("hash", lastHash);
        socket.emit("ok");
      }
    });
  }
  listen(port) {
    this.server.listen(port, () => {
      console.log(port + "服务启动成功!");
    });
  }
}
let server = new Server(compiler);
server.listen(8080);

webpack-hot-dev-client.js

  • 客户端启动一个 socket 监听服务器返回的 hash, 通过 jsonp 请求新文件,实现热更新
js
let socket = io("/");
let currentHash;
let hotCurrentHash;
const onConnected = () => {
  console.log("客户端已经连接");
  socket.on("hash", (hash) => {
    currentHash = hash;
  });
  socket.on("ok", () => {
    hotCheck();
  });
  socket.on("disconnect", () => {
    hotCurrentHash = currentHash = null;
  });
};
function hotCheck() {
  if (!hotCurrentHash || hotCurrentHash === currentHash) {
    return (hotCurrentHash = currentHash);
  }
  hotDownloadManifest().then((update) => {
    let chunkIds = Object.keys(update.c);
    chunkIds.forEach((chunkId) => {
      hotDownloadUpdateChunk(chunkId);
    });
  });
}

function hotDownloadUpdateChunk(chunkId) {
  var script = document.createElement("script");
  script.charset = "utf-8";
  script.src = "/" + chunkId + "." + hotCurrentHash + ".hot-update.js";
  document.head.appendChild(script);
}
function hotDownloadManifest() {
  var url = "/" + hotCurrentHash + ".hot-update.json";
  return fetch(url)
    .then((res) => res.json())
    .catch((error) => {
      console.log(error);
    });
}
window.webpackHotUpdate = (chunkId, moreModules) => {
  for (let moduleId in moreModules) {
    let oldModule = __webpack_require__.c[moduleId];
    let { parents, children } = oldModule;
    var module = (__webpack_require__.c[moduleId] = {
      i: moduleId,
      exports: {},
      parents,
      children,
      hot: window.hotCreateModule(),
    });
    moreModules[moduleId].call(
      module.exports,
      module,
      module.exports,
      __webpack_require__
    );
    parents.forEach((parent) => {
      let parentModule = __webpack_require__.c[parent];
      parentModule.hot &&
        parentModule.hot._acceptedDependencies[moduleId] &&
        parentModule.hot._acceptedDependencies[moduleId]();
    });
    hotCurrentHash = currentHash;
  }
};
socket.on("connect", onConnected);
window.hotCreateModule = () => {
  var hot = {
    _acceptedDependencies: {},
    _acceptedDependencies: function (dep, callback) {
      for (var i = 0; i < dep.length; i++) {
        hot._acceptedDependencies[dep[i]] = callback;
      }
    },
  };
  return hot;
};

如何监听文件发生变化

轮询判断文件的最后编辑时间是否变化,某个文件发生了变化,并不会立刻告诉监听者,而是先缓存起来,等 aggregateTimeout (监听到变化发生后会等到 300ms 再去执行,默认 300ms)

在 MIT 许可下发布